/*
-----------------------------------------------------------------------------
This source file is part of DIVA
The project of Software Engineering Lab, dept. of CSIE, NTNU.
Also the project of Object-Oriented Lab, dept. of CSIE, NCU.

Copyright (c) 2000-2010 The Software Engineering Lab, dept. of CSIE, NTNU.
Copyright (c) 2011-2012 The Object-Oriented Lab, dept. of CSIE, NCU.
-----------------------------------------------------------------------------
Filename:    DIVA_Application.cpp
Description: The file implements all the method and initialise all the variables
defined in the DIVA_Application.h.
-----------------------------------------------------------------------------
*/
#include "DIVA_Application.h"
#include "DIVA_Manager.h"
#include "QDivaAppController.h"
#include "QOgreStats.h"
#include "QLogger.h"
#include "MappingDialog.h"
#include "DIVA_FrameListener.h"
#include "QxDivaController.h"
#include <xTitanClient/xTitan.hpp>
#include <QMessageBox> // for showing message
#include <QStringList>
#include <QInputDialog>
#include <QKeyEvent>
#include "OLogListener.h"
#include "OgreLogManager.h"
#include "DIVA_RenderData.h"
#include <QDebug>
#include <QThread>



HWND _hWnd ; // the global variable to be used by mapping engine GUI
DIVA_Application* DIVA_Application::instance = NULL;
/*instance = NULL; //add by Jill 2013,04,21*/
//************************************
// Method:    DIVA_Application
// FullName:  DIVA_Application::DIVA_Application
// Access:    protected
// Description:
//// The constructor of the DIVA_Application
//// Set the FrameListener and the Root to be null
//// Also set some Qt attributes to disable double buffering by Qt
//// It's according to http://crashcoder.blogspot.com/2008/11/qt-and-ogre.html
//************************************
DIVA_Application::DIVA_Application(void){

	mFrameListener = 0;
	mRoot = 0;
	_messageBox = new QMessageBox();
	setAttribute(Qt::WA_PaintOnScreen);
	setAttribute(Qt::WA_OpaquePaintEvent);
	setAttribute(Qt::WA_NoSystemBackground);
	setAutoFillBackground( false );	
	setMouseTracking(false);
}

//destructor
DIVA_Application::~DIVA_Application(void){

	// modified by howway
	//if (mFrameListener)
	//	delete mFrameListener;
	//if (mRoot)
	//	delete mRoot;
}



// The call to Root::startRendering() is no longer needed since we're using Qt's event loop now.
void DIVA_Application::go(){

#ifdef _DEBUG
	mResourcesCfg = "resources_d.cfg";
	mPluginsCfg = "plugins_d.cfg";	
#else
	mResourcesCfg = "resources.cfg";
	mPluginsCfg = "plugins.cfg";
#endif

	setup();	
}

// singleton
DIVA_Application *DIVA_Application::getInstance()
{
	if(instance == NULL)
		instance = new DIVA_Application();

	return instance;
}

void DIVA_Application::paintEvent(QPaintEvent *event){
	//updateRenderWindow();
}

void DIVA_Application::updateRenderWindow(){
	assert(this->mWindow);
	assert(this->mRoot);
	this->mRoot->_fireFrameStarted();
	this->mWindow->update();
	this->mRoot->_fireFrameEnded();	
}

void DIVA_Application::setup(){
	this->newMappingDialog();

	mRoot = new Ogre::Root(mPluginsCfg);
	setupResources();
	bool carryOn = configure();//show config dialog
	if (!carryOn) {
		QMessageBox::critical(this, "Configuration Abandoned", 
			"Configuration skipped, unable to start the application.");
		std::exit(1);
	}
	createRenderWindow();
	createSceneManager();
	createCamera("Diva_Camera");
	DIVA_Manager::init(mSceneMgr);//Create DIVA_Manager
	createViewports();// Create Viewports

	// Set default mipmap level (NB some APIs ignore this)
	Ogre::TextureManager::getSingleton().setDefaultNumMipmaps(5);

	createResourceListener();// Create any resource listeners (for loading screens)
	loadResources();
	createScene();	
	createFrameListener();// Create the default FrameListener	
	startTimer(10);

	initController();


	this->setFocusPolicy( Qt::StrongFocus ); // for accepting Qt keyboard events	
}

//************************************
// Method:    configure
// FullName:  DIVA_Application::configure
// Access:    protected 
// Returns:   bool
// Description:
//// Show the configuration dialog and initialise the system
//// You can skip this and use root.restoreConfig() to load configuration
//// settings if you were sure there are valid ones saved in ogre.cfg
//************************************
bool DIVA_Application::configure(void){
	assert(this->mRoot);

	bool ok = mRoot->restoreConfig();

	//////////////////////////////////////////////////////////////////////////
	// Story:
	// During testing, the config dialog will delay the input event sent by
	// OracleServer and will very likely produce incorrect output events.
	// Therefore, if it is in testing mode, we use default config settings
	// instead of popping out config dialog.
	//////////////////////////////////////////////////////////////////////////
	if( !ok ) {
		ok = mRoot->showConfigDialog();
	}

	if(ok){
		mRoot->initialise(false);
	} else{
		assert(false);
	}

	return ok;
}

//************************************
// Method:    createSceneManager
// FullName:  DIVA_Application::createSceneManager
// Access:    protected
// Description:
////Create the SceneManager, in this case a generic one
//************************************
void DIVA_Application::createSceneManager(void){
	assert(this->mRoot);
	mSceneMgr = mRoot->createSceneManager(Ogre::ST_GENERIC);
}

//************************************
// Method:    createCamera
// FullName:  DIVA_Application::createCamera
// Access:    protected 
// Parameter: std::string name
// Description:
//// 1. Create the camera
//// 2. Position it at 500 in Z direction
//// 3. Look back along -Z
//// 4. Set the nearest distance
//************************************

void DIVA_Application::createCamera(std::string name){
	assert(this->mSceneMgr);
	mCamera = mSceneMgr->createCamera(name);
	mCamera->setPosition(Ogre::Vector3(0,0,500));
	mCamera->lookAt(Ogre::Vector3(0,0,-300));
	mCamera->setNearClipDistance(CAMERA_DISTANCE);
}

//************************************
// Method:    createFrameListener
// FullName:  DIVA_Application::createFrameListener
// Access:    protected 
// Description:
//// Create the FrameListener and use both the mouse and key buffered input
//************************************
void DIVA_Application::createFrameListener(void){
	assert(this->mRoot);
	mFrameListener= new DIVA_FrameListener(mSceneMgr,mWindow, mCamera);
	mRoot->addFrameListener(mFrameListener);
}

/** The createScene method - */
void DIVA_Application::createScene(void){
	assert(this->mSceneMgr);
	mSceneMgr->setShadowTechnique(Ogre::SHADOWTYPE_TEXTURE_MODULATIVE);
	mSceneMgr->setShadowFarDistance(1000);
	Ogre::MovableObject::setDefaultVisibilityFlags(0x00000001);
	mSceneMgr->setAmbientLight(Ogre::ColourValue(0.7, 0.7, 0.5));
	Ogre::Light* l = mSceneMgr->createLight("Light2");
	Ogre::Vector3 dir(-1,-1,0);
	dir.normalise();
	l->setType(Ogre::Light::LT_DIRECTIONAL);
	l->setDirection(dir);
	l->setDiffuseColour(1, 1, 0.8);
	l->setSpecularColour(1, 1, 1);
	initSkyBox();
}


//************************************
// Method:    createViewports
// FullName:  DIVA_Application::createViewports
// Access:    protected 
// Description:
//// 1. Create one viewport, entire window
//// 2. Alter the camera aspect ratio to match the viewport
//************************************
void DIVA_Application::createViewports(void){
	assert(mWindow);
	assert(mCamera);
	Ogre::Viewport* vp = mWindow->addViewport(mCamera);
	vp->setBackgroundColour(Ogre::ColourValue(0,0,0));
	mCamera->setAspectRatio(Ogre::Real(vp->getActualWidth()) / Ogre::Real(vp->getActualHeight()));
}


//************************************
// Method:    setupResources
// FullName:  DIVA_Application::setupResources
// Access:    protected 
// Description:
//// Define the sources of resources (other than current folder)
//// 1. Load resource paths from config file
//// 2. Go through all sections and settings in the file
//************************************
void DIVA_Application::setupResources(void){
	Ogre::ConfigFile cf;
#if _DEBUG
	cf.load("resources_d.cfg");
#else
	cf.load("resources.cfg");
#endif
	Ogre::ConfigFile::SectionIterator seci = cf.getSectionIterator();
	Ogre::String secName, typeName, archName;
	while (seci.hasMoreElements())
	{
		secName = seci.peekNextKey();
		Ogre::ConfigFile::SettingsMultiMap *settings = seci.getNext();
		Ogre::ConfigFile::SettingsMultiMap::iterator i;
		for (i = settings->begin(); i != settings->end(); ++i)
		{
			typeName = i->first;
			archName = i->second;
			Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
				archName, typeName, secName);
		}
	}
}

// Optional override method where you can create resource listeners (e.g. for loading screens)
void DIVA_Application::createResourceListener(void){
}

/// Optional override method where you can perform resource group loading
/// Must at least do ResourceGroupManager::getSingleton().initialiseAllResourceGroups();
void DIVA_Application::loadResources(void){
	// Initialize, parse scripts etc
	Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups();	
}

//************************************
// Method:    createRenderWindow
// FullName:  DIVA_Application::createRenderWindow
// Access:    protected
// Description:
//// Makes the render target to our Qt Widget
//// Since "parentWindowHandle" made our window unable to handle mouse events,
//// now we use "externalWindowHandle" instead.
//************************************
void DIVA_Application::createRenderWindow(){
	assert(mRoot);
	Ogre::NameValuePairList params;
	params["left"] = "0";
	params["top"] = "0";
	params["border"] = "none";
	params["externalWindowHandle"] = Ogre::StringConverter::toString( (size_t) (HWND) this->winId() );
	mWindow = mRoot->createRenderWindow( "DIVA_Qt", width(), height(), false, &params);
}

//************************************
// Method:    resizeEvent
// FullName:  DIVA_Application::resizeEvent
// Access:    protected 
// Parameter: QResizeEvent * e
// Description:
//// Whenever a resize event happens, tell the render window to resize.
//************************************
void DIVA_Application::resizeEvent( QResizeEvent* e)
{
	assert(this->mWindow);
	this->mWindow->windowMovedOrResized();
}

//************************************
// Method:    timerEvent
// FullName:  DIVA_Application::timerEvent
// Access:    protected 
// Parameter: QTimerEvent * e
// Description:
//// Whenever a timer event happens, tell the render window to update itself.
//************************************
void DIVA_Application::timerEvent( QTimerEvent* e )
{
	mRoot->renderOneFrame();
	// this->updateRenderWindow();
	// Make it able to show Ogre related stats
	this->updateOgreStats();
	// Clean the keyboard actions whenever timeout happens
	this->_controller->stopWheelZooming();
}


void DIVA_Application::keyPressEvent( QKeyEvent* evt ){
	this->_controller->keyboardPressed(evt->key());
}

void DIVA_Application::keyReleaseEvent( QKeyEvent* evt ){
	this->_controller->keyboardReleased(evt->key());
}

void DIVA_Application::mousePressEvent( QMouseEvent* evt ){
	if (evt->button()==Qt::LeftButton){
		this->_controller->mouseLeftButtonPressed(evt->x(), evt->y());
	} else if (evt->button()==Qt::RightButton){
		this->_controller->mouseRightButtonPressed(evt->x(), evt->y());
	} else{
		QLogger::getInstance()->append(
			QString("Unknown mouse button pressed: %1")
			.arg(QString::number(evt->button())));
	}
}

void DIVA_Application::mouseReleaseEvent( QMouseEvent* evt ){
	if (evt->button()==Qt::LeftButton){
		this->_controller->mouseLeftButtonReleased(evt->x(), evt->y());
	} else if (evt->button()==Qt::RightButton){
		this->_controller->mouseRightButtonReleased(evt->x(), evt->y());
	} else{
		QLogger::getInstance()->append(
			QString("Unknown mouse button pressed: %1")
			.arg(QString::number(evt->button())));
	}
}

void DIVA_Application::mouseMoveEvent( QMouseEvent* evt ){	
	this->_controller->mouseMoving( evt->x(), evt->y() );	
}

void DIVA_Application::wheelEvent( QWheelEvent* evt ){
	this->_controller->mouseWheelRotated( evt->delta() );
}

void DIVA_Application::changeBackground(){	
	bool ok;
	QString item = QInputDialog::getItem(this, "Change Background",
		"Select background: ", _skyBoxList, 0, false, &ok);
	if (ok && !item.isEmpty()){		
		int index = _skyBoxList.indexOf(item);
		_skyBoxList.move(index, 0); // Move the selected sky box name to the first of the list
		this->_controller->changeBackground( _skyBoxList.first() );
	}
}

//************************************
// Method:    initSkyBox
// FullName:  DIVA_Application::initSkyBox
// Access:    protected 
// Returns:   void
// Story:     Initialize the list that maintains the string representation of background setting.
//            The strings are defined in $(OGRE_HOME)\media\materials\scripts\Examples.material
//            Append the string you found in the Examples.material file to the _skyBoxList 
//            whenever you want to add a new background.
// Note:      Sometimes calling QString::toStdString() would result in inserting unknown characters
//            in front of the original string. To avoid this, turning the original QString to a
//            UTF-8 QByteArray is necessary.
//************************************
void DIVA_Application::initSkyBox(){
	_skyBoxList << SKYBOX_MORNING;
	_skyBoxList << SKYBOX_STORMY;
	_skyBoxList << "TrippySkyBox";
	_skyBoxList << "SpaceSkyBox";

	assert(!_skyBoxList.isEmpty());
	std::string skyBox = _skyBoxList.first().toUtf8().data();
	mSceneMgr->setSkyBox(true, SKYBOX_LOCATION + skyBox );
}

void DIVA_Application::updateOgreStats(){
	// This method was found in DIVA_FrameListener
	// However it used CEGUI to display Ogre-related stats
	// Now we've changed GUI framework to Qt, so we also use a QWidget to display the stats

	QOgreStats *ogreStat = QOgreStats::getInstance();

	const Ogre::RenderTarget::FrameStats& stats = mWindow->getStatistics();
	ogreStat->updateAvgFPS( stats.avgFPS );
	ogreStat->updateBestFPS( stats.bestFPS );
	ogreStat->updateCurrentFPS( stats.lastFPS );
	ogreStat->updateTriCount( stats.triangleCount );
	ogreStat->updateWorstFPS( stats.worstFPS );

	float avg = 1.0f/(stats.avgFPS + 1.0f);
	if (avg > 0.1f) 
		avg = 0.1f;
	
	if( xIsTesting() == false ){
		DIVA_RenderData::getSingletonPtr()->mAvgFrameTime = avg;
	}
	
}

void DIVA_Application::switchFrameListener( bool turnedOn ){
	mFrameListener->switchFrameListener(turnedOn);
}


MappingDialog * DIVA_Application::newMappingDialog()
{
	_mappingDialog = new MappingDialog;
	_mappingDialog->setWindowFlags(Qt::WindowMinMaxButtonsHint);


	return this->_mappingDialog;
}


MappingDialog * DIVA_Application::getMappingDialog() const
{
	return this->_mappingDialog;
}

void DIVA_Application::initController(){
	if ( xIsTesting() ){ //use testing-purposed controller
		this->_controller = new QxDivaController();
	} else{ // Use normal controller. 
		this->_controller = new QDivaAppController();
	}
	this->_controller->init(this->mSceneMgr, this->mCamera);
}

QDivaAppController* DIVA_Application::getController(){
	return this->_controller;
}

QMessageBox* DIVA_Application::getMessageBox()
{
	return this->_messageBox;
}
Ogre::RenderWindow* DIVA_Application::getMwindow(){
	assert(mWindow!= NULL);
	return mWindow;
}
